﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using AppToolkit.WF.Services;
using System.Activities;
using System.Threading;
using System.Activities.Tracking;

namespace AppToolkit.WF.Tests.Services
{
    [TestFixture]
    public class WorkflowStateTrackingParticipantTests
    {
        private WorkflowStateTrackingParticipant _target;

        
        [SetUp]
        public void Setup()
        {
            this._target = new WorkflowStateTrackingParticipant();
        }

        [TearDown]
        public void Dispose()
        {
            this._target = null;
        }


        [Test]
        public void Workflow_collection_is_empty()
        {
            Assert.AreEqual(0, this._target.WorkflowStates.Count);
        }

        [Test]
        public void Execute_WF_completion()
        {
            var inv = new WorkflowInvoker(new TestWF());

            var inputs = new Dictionary<string, object>() { { "Operation", "Normal" } };
            inv.Extensions.Add(this._target);
            inv.Invoke(inputs);            

            Assert.IsTrue(this._target.WorkflowStates.Count > 0);
            Assert.AreEqual("Completed", ((WorkflowState)this._target.WorkflowStates.First()).State);
        }

        [Test]
        public void Execute_WF_cancel()
        {
            WorkflowApplication inv;
            AutoResetEvent syncEvent = initRuntime(out inv);
            inv.Run();

            inv.Cancel();
            syncEvent.WaitOne();

            Assert.IsTrue(this._target.WorkflowStates.Count > 0);
            Assert.AreEqual("Canceled", ((WorkflowState)this._target.WorkflowStates.First()).State);

        }

        [Test]
        public void Execute_WF_terminated()
        {
            WorkflowApplication inv;
            AutoResetEvent syncEvent = initRuntime(out inv);
            inv.Run();

            inv.Terminate("reason");
            syncEvent.WaitOne();

            Assert.IsTrue(this._target.WorkflowStates.Count > 0);
            Assert.AreEqual("Terminated", ((WorkflowState)this._target.WorkflowStates.First()).State);
        }

        [Test]
        public void Execute_WF_Unloaded()
        {
            WorkflowApplication inv;
            AutoResetEvent syncEvent = initRuntime(out inv);
            inv.InstanceStore = new NullInstanceStore();
            inv.Run();

            inv.Unload();
            syncEvent.WaitOne();

            Assert.IsTrue(this._target.WorkflowStates.Count > 0);
            Assert.AreEqual("Unloaded", ((WorkflowState)this._target.WorkflowStates.First()).State);

        }

        [Test]
        public void Execute_WF_Progress()
        {
            const int INT_Max = 20;
            var inputs = new Dictionary<string, object>() { 
                { "Iterations", INT_Max },
            };

            var inv = new WorkflowApplication(new StateTrackingWF(), inputs);
            inv.Extensions.Add(this._target);

            var syncEvent = new AutoResetEvent(false);

            // Handle the desired lifecycle events.
            inv.Completed = (e) => syncEvent.Set();
            inv.Unloaded = (e) => syncEvent.Set();

            var wfTrackingParticipant = new WorkflowStateTrackingParticipant();
            inv.Extensions.Add(wfTrackingParticipant);

            inv.Run();

            syncEvent.WaitOne();

            var wf = this._target.WorkflowStates.First();
            Assert.AreEqual("Completed", wf.State);
            Assert.AreEqual(INT_Max, wf.Current);
            Assert.AreEqual(INT_Max, wf.Maximum);
        }

        [Test]
        public void Can_register_more_than_one_TrackingParticipant()
        {
            const int INT_Max = 20;
            var inputs = new Dictionary<string, object>() { 
                { "Iterations", INT_Max },
            };
            
            var inv = new WorkflowApplication(new StateTrackingWF(), inputs);
            inv.Extensions.Add(this._target);

            var syncEvent = new AutoResetEvent(false);

            // Handle the desired lifecycle events.
            inv.Completed = (e) => syncEvent.Set();
            inv.Unloaded = (e) => syncEvent.Set();


            var newEtwTrackingParticipant = new System.Activities.Tracking.EtwTrackingParticipant();

            var q = new System.Activities.Tracking.ActivityStateQuery()
                    {
                        ActivityName = "*",
                    };

            q.States.Add(WorkflowInstanceStates.Started);
            q.States.Add(WorkflowInstanceStates.Completed);
            q.States.Add(WorkflowInstanceStates.Unloaded);

            newEtwTrackingParticipant.TrackingProfile = new System.Activities.Tracking.TrackingProfile()
            {
                Name = "EtwTrackingProfile",
                Queries = { q }
            };

            inv.Extensions.Add(newEtwTrackingParticipant);
            
            var wfTrackingParticipant = new WorkflowStateTrackingParticipant();
            inv.Extensions.Add(wfTrackingParticipant);

            inv.Run();

            syncEvent.WaitOne();

            var wf = this._target.WorkflowStates.First();
            Assert.AreEqual("Completed", wf.State);
            Assert.AreEqual(INT_Max, wf.Current);
            Assert.AreEqual(INT_Max, wf.Maximum);
        }

        #region Support
        private AutoResetEvent initRuntime(out WorkflowApplication inv)
        {
            return initRuntime(null, out inv);
        }

        private AutoResetEvent initRuntime(IDictionary<string, object> inputs, out WorkflowApplication inv)
        {
            var wf = new TestWF();
            inv = (inputs == null) ? new WorkflowApplication(wf) : new WorkflowApplication(wf, inputs);

            inv.Extensions.Add(this._target);

            var syncEvent = new AutoResetEvent(false);
            
            // Handle the desired lifecycle events.
            inv.Completed = (e) => syncEvent.Set();
            inv.Unloaded = (e) => syncEvent.Set();

            return syncEvent;
        }

        public class NullInstanceStore : System.Runtime.DurableInstancing.InstanceStore
        {
            readonly Guid _ownerID = Guid.NewGuid();

            protected override IAsyncResult BeginTryCommand(System.Runtime.DurableInstancing.InstancePersistenceContext context, System.Runtime.DurableInstancing.InstancePersistenceCommand command, TimeSpan timeout, AsyncCallback callback, object state)
            {
                if (command is System.Activities.DurableInstancing.CreateWorkflowOwnerCommand)
                {
                    context.BindInstanceOwner(_ownerID, Guid.NewGuid());
                }
                return new AppToolkit.WF.Utils.CompletedAsyncResult<bool>(true, (a) => { }, null);
            }

            protected override bool EndTryCommand(IAsyncResult result)
            {
                return AppToolkit.WF.Utils.CompletedAsyncResult<bool>.End(result);
            }

            //Synchronous version of the Begin/EndTryCommand functions
            protected override bool TryCommand(System.Runtime.DurableInstancing.InstancePersistenceContext context,
                System.Runtime.DurableInstancing.InstancePersistenceCommand command, TimeSpan timeout)
            {
                return EndTryCommand(BeginTryCommand(context, command, timeout, null, null));
            }
        } 
        #endregion

    }
}
